﻿' ******************************************************************************
' ** 
' **  Yahoo! Managed
' **  Written by Marius Häusler 2011
' **  It would be pleasant, if you contact me when you are using this code.
' **  Contact: YahooFinanceManaged@gmail.com
' **  Project Home: http://code.google.com/p/yahoo-finance-managed/
' **  
' ******************************************************************************
' **  
' **  Copyright 2011 Marius Häusler
' **  
' **  Licensed under the Apache License, Version 2.0 (the "License");
' **  you may not use this file except in compliance with the License.
' **  You may obtain a copy of the License at
' **  
' **    http://www.apache.org/licenses/LICENSE-2.0
' **  
' **  Unless required by applicable law or agreed to in writing, software
' **  distributed under the License is distributed on an "AS IS" BASIS,
' **  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
' **  See the License for the specific language governing permissions and
' **  limitations under the License.
' ** 
' ******************************************************************************


Namespace YahooManaged.Finance.Support

    ''' <summary>
    ''' Class for managing stock exchange information. 
    ''' </summary>
    ''' <remarks></remarks>
    Public Class StockExchange
        Private mID As String = String.Empty
        Private mName As String = String.Empty
        Private mSuffix As String = String.Empty
        Private mCountry As CountryInfo = Nothing

        Private mTradingTime As TradingTimeInfo = Nothing

        ''' <summary>
        ''' The ID of the exchange
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks>If the ID is in WorldMarket.DefaultStockExchanges, properties will be setted automatically</remarks>
        Public ReadOnly Property ID() As String
            Get
                Return mID
            End Get
        End Property

        ''' <summary>
        ''' The ending string for stock IDs
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks>If the suffix is in DefaultStockExchanges, properties will get automatically</remarks>
        Public ReadOnly Property Suffix() As String
            Get
                Return mSuffix
            End Get
        End Property

        ''' <summary>
        ''' The name of the exchange
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public ReadOnly Property Name() As String
            Get
                Return mName
            End Get
        End Property


        Public ReadOnly Property Country As CountryInfo
            Get
                Return mCountry
            End Get
        End Property


        Public ReadOnly Property TradingTime As TradingTimeInfo
            Get
                Return mTradingTime
            End Get
        End Property

        Private ReadOnly mTags As New List(Of String)
        Friend ReadOnly Property Tags() As List(Of String)
            Get
                Return mTags
            End Get
        End Property

        ''' <summary>
        ''' Default constructor
        ''' </summary>
        ''' <param name="id"></param>
        ''' <param name="suffix"></param>
        ''' <remarks></remarks>
        Public Sub New(ByVal id As String, ByVal suffix As String, ByVal name As String, ByVal country As CountryInfo, ByVal tradeTime As TradingTimeInfo)
            If id <> String.Empty Then
                mID = id
            Else
                Throw New ArgumentNullException("id", "The ID is empty.")
            End If

            mSuffix = suffix
            mName = name
            If country IsNot Nothing Then
                mCountry = country
            Else
                Throw New ArgumentNullException("country", "The country is null.")
            End If

            If tradeTime IsNot Nothing Then
                mTradingTime = tradeTime
            Else
                Throw New ArgumentNullException("tradeTime", "The trade time is null.")
            End If
        End Sub
        Friend Sub New(ByVal se As StockExchange)
            If se Is Nothing Then
                Throw New ArgumentNullException("se", "Original StockExchange is null.")
            Else
                If se IsNot Nothing Then
                    mID = se.ID
                    mCountry = se.Country
                    mSuffix = se.Suffix
                    mName = se.Name
                    With se.TradingTime
                        mTradingTime = New TradingTimeInfo(.DelayMinutes, .TradingDays, .Holidays, .LocalOpeningTime, .TradingSpan, .UtcOffsetStandardTime, .DaylightSavingTimes)
                    End With
                End If
            End If
        End Sub

        ''' <summary>
        ''' Returns the name of the stock exchange
        ''' </summary>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Overrides Function ToString() As String
            Return mName
        End Function

    End Class


    Public Class TradingTimeInfo
        Private mDelayMinutes As Integer = 0
        Private mTradingDays As New List(Of DayOfWeek)
        Private mHolidays As New List(Of Date)
        Private mLocalOpeningTime As DateTime = #12:00:00 PM#
        Private mTradingSpan As New TimeSpan(24, 0, 0)
        Private mUtcOffsetStandardTime As Integer = 0
        Private mDaylightSavingTimes(-1) As DaylightSavingTime

        ''' <summary>
        ''' The data response delay to realtime of yahoo servers
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public ReadOnly Property DelayMinutes() As Integer
            Get
                Return mDelayMinutes
            End Get
        End Property
        ''' <summary>
        ''' The days when trading is active
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public ReadOnly Property TradingDays() As List(Of DayOfWeek)
            Get
                Return mTradingDays
            End Get
        End Property
        ''' <summary>
        ''' Days without active trading time.
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public ReadOnly Property Holidays() As List(Of Date)
            Get
                Return mHolidays
            End Get
        End Property
        ''' <summary>
        ''' The time when trading starts
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks>By setting a value, the date is not important, only hour and minute</remarks>
        Public ReadOnly Property LocalOpeningTime() As DateTime
            Get
                Return mLocalOpeningTime
            End Get
        End Property
        ''' <summary>
        ''' The timespan of active trading for each trading day
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public ReadOnly Property TradingSpan As TimeSpan
            Get
                Return mTradingSpan
            End Get
        End Property
        ''' <summary>
        ''' The time when trading ends
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks>By setting a value, the date is not important, only hour and minute. If time value is smaler than opening, trading ends on the next day. 24 hours trading is maximum</remarks>
        Public ReadOnly Property LocalClosingTime() As DateTime
            Get
                Return mLocalOpeningTime.Add(mTradingSpan)
            End Get
        End Property
        ''' <summary>
        ''' The number of hours relative to UTC of the exchange's timezone
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public ReadOnly Property UtcOffsetStandardTime() As Integer
            Get
                Return mUtcOffsetStandardTime
            End Get
        End Property
        Public ReadOnly Property DaylightSavingTimes() As DaylightSavingTime()
            Get
                Return mDaylightSavingTimes
            End Get
        End Property

        Public Sub New(ByVal delayMinutes As Integer, ByVal tradingDays As IEnumerable(Of DayOfWeek), ByVal holidays As IEnumerable(Of Date), ByVal localOpeningTime As DateTime, ByVal tradingSpan As TimeSpan, ByVal utcOffset As Integer)
            If delayMinutes >= 0 And delayMinutes < 3600 Then
                mDelayMinutes = delayMinutes
            Else
                Throw New ArgumentException("The delay in minutes must be minimum 0 and maximum 3600.", "delayMinutes")
            End If

            If tradingDays IsNot Nothing Then
                mTradingDays.AddRange(tradingDays)
            End If

            If holidays IsNot Nothing Then
                mHolidays.AddRange(holidays)
            End If

            mLocalOpeningTime = #12:00:00 AM#.AddHours(localOpeningTime.Hour).AddMinutes(localOpeningTime.Minute)

            If tradingSpan.TotalMinutes > 0 And tradingSpan.TotalMinutes <= 3600 Then
                mTradingSpan = tradingSpan
            ElseIf tradingSpan.TotalMinutes = 0 Then
                mTradingSpan = New TimeSpan(24, 0, 0)
            Else
                Throw New ArgumentException("The trading span must be within 24 hours", "tradingSpan")
            End If

            If utcOffset >= -12 And utcOffset <= 12 Then
                mUtcOffsetStandardTime = utcOffset
            Else
                Throw New ArgumentException("The UTC offset must be between -12 and +12", "utcOffset")
            End If

        End Sub
        Public Sub New(ByVal delayMinutes As Integer, ByVal tradingDays As IEnumerable(Of DayOfWeek), ByVal holidays As IEnumerable(Of Date), ByVal localOpeningTime As DateTime, ByVal tradingSpan As TimeSpan, ByVal utcOffset As Integer, ByVal dst() As DaylightSavingTime)
            Me.New(delayMinutes, tradingDays, holidays, localOpeningTime, tradingSpan, utcOffset)
            If dst IsNot Nothing Then
                mDaylightSavingTimes = dst
            End If
        End Sub


        ''' <summary>
        ''' Returns if trading is active at a specific datetime in relation to stock exchange's current timezone.
        ''' </summary>
        ''' <param name="time">The DateTime of the stock exchange's local time zone.</param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Function IsActiveExchangeLocal(ByVal time As DateTime) As Boolean
            Dim isHoliday As Boolean = False
            If mHolidays IsNot Nothing AndAlso mHolidays.Count > 0 Then
                For Each h As Date In mHolidays
                    If h.Date = time.Date Then
                        isHoliday = True
                        Exit For
                    End If
                Next
            End If

            If Not isHoliday AndAlso mTradingDays IsNot Nothing AndAlso mTradingDays.Contains(time.DayOfWeek) Then
                Dim localStartTime As DateTime = time.Date.AddHours(mLocalOpeningTime.Hour).AddMinutes(mLocalOpeningTime.Minute)
                Dim localEndTime As DateTime = localStartTime.Add(mTradingSpan)
                Return (time > localStartTime And time < localEndTime)
            Else
                Return False
            End If
        End Function

        Public Function ActualExchangeLocalTime() As DateTime
            Dim time = Date.UtcNow
            Dim dstOffset As Integer = 0
            If mDaylightSavingTimes.Length > 0 Then
                For Each dst In mDaylightSavingTimes
                    If dst.Year = time.Year Then
                        If dst.StartDate < dst.EndDate Then
                            If time > dst.StartDate And time < dst.EndDate Then
                                dstOffset += 1
                            End If
                        Else
                            If time < dst.StartDate Or time > dst.EndDate Then
                                dstOffset += 1
                            End If
                        End If
                    End If
                Next
            End If
            Return time.AddHours(mUtcOffsetStandardTime + dstOffset)
        End Function

        ''' <summary>
        ''' Returns if trading is active at a specific UTC DateTime.
        ''' </summary>
        ''' <param name="time">The UTC DateTime</param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Function IsActiveUTC(ByVal time As DateTime) As Boolean
            Dim dstOffset As Integer = 0
            If mDaylightSavingTimes.Length > 0 Then
                For Each dst In mDaylightSavingTimes
                    If dst.Year = time.Year Then
                        If dst.StartDate < dst.EndDate Then
                            If time > dst.StartDate And time < dst.EndDate Then
                                dstOffset += 1
                            End If
                        Else
                            If time < dst.StartDate Or time > dst.EndDate Then
                                dstOffset += 1
                            End If
                        End If
                    End If
                Next
            End If
            Return Me.IsActiveExchangeLocal(time.AddHours(mUtcOffsetStandardTime + dstOffset))
        End Function

        Public Function IsActiveMachineLocal(ByVal time As DateTime) As Boolean
            Return Me.IsActiveUTC(time.ToUniversalTime)
        End Function





    End Class

End Namespace